/****************************************************
          File Name:tools.c
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Mon 10 Nov 2014 09:29:52 AM CST
*****************************************************/

#include "header.h"


struct {
	void *start;
	size_t length;
} *buffers = NULL;


static void err(char *fun)
{
	perror(fun);
	exit(EXIT_FAILURE);
}

static int myioctl(int fd, int request, void *arg)
{
	int res;
	do
	{
		res = ioctl(fd, request, arg);
	}while(errno == EINTR && res == -1);
	
	return res;
}

void dev_open(void)
{
	struct stat dev_stat;
	
	stat(device, &dev_stat);
	
	if(!S_ISCHR(dev_stat.st_mode))
		err("EEEOR");
	
	fd = open(device, O_RDWR | O_NONBLOCK);
	
	if(fd == -1)
		err("ERROR");
}

void dev_close(void)
{
	if(close(fd) == -1)
		err("close");
	
	fd = -1;
}

void dev_init(void)
{
	struct v4l2_capability cap;
	struct v4l2_cropcap cropcap;
	struct v4l2_crop crop;
	struct v4l2_format format;
	struct v4l2_requestbuffers reqbuf;
	struct v4l2_streamparm parm;
	int min;
	int prot = PROT_READ | PROT_WRITE;
	
	CLEAN(cap);
	CLEAN(format);
	CLEAN(reqbuf);
	CLEAN(parm);

/*******  if the device support v4l2 and support video capture and streaming ********/
	
	if(myioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
	{
		if(errno == EINVAL)
		{
			fprintf(stderr, "%s no supported by v4l2\n", device);
			exit(EXIT_FAILURE);
		}
		else
			err("VIDIOC_QUERCAP");
	}
	
	if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
	{
		fprintf(stderr, "%s is not support video capture\n", device);
		exit(EXIT_FAILURE);
	}
	
	if(!(cap.capabilities & V4L2_CAP_STREAMING))
	{
		fprintf(stderr, "%s does not support video streaming\n", device);
		exit(EXIT_FAILURE);
	}

/**************		 cropcap -- crop		**************/

	CLEAN(cropcap);
	
	cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	if(myioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0)
	{
		crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		crop.c = cropcap.defrect;					/* default rectangle */
		
		if(myioctl(fd, VIDIOC_S_CROP, &crop) == -1)
		{
			if(errno == EINVAL)
				fprintf(stderr, "not support crop\n");
		}
	}
	else
		perror("VIDIOC_CROPCAP");

/**************     get default frame rate ****************/
	parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	if(myioctl(fd, VIDIOC_G_PARM, &parm) == -1)
		err("G_PARM");
	printf("分母 : %u \n",parm.parm.capture.timeperframe.denominator);
	printf("分子 : %u \n",parm.parm.capture.timeperframe.numerator);
	

/*****************		determine the data format		**********************/

	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	format.fmt.pix.width = 640;
	format.fmt.pix.height = 480;
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 		//   yuv422, my camera cannot support yuv420
	format.fmt.pix.field = V4L2_FIELD_INTERLACED;
	
	if(myioctl(fd, VIDIOC_S_FMT, &format) == -1)	/* fill up rest members of format */
		err("VIDIOC_S_FMT");
/*
	if(myioctl(fd, VIDIOC_G_FMT, &format) == -1)	// get info 
		err("VIDIOC_S_FMT");
	if(format.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
		printf(" sooooo bad , can NOT support yuv420\e[0m\n");
	else
		printf("\e[0m Great !! It supports YUV420\e[0m\n");
*/

	min = format.fmt.pix.width * 2;
	if(format.fmt.pix.bytesperline < min)
		format.fmt.pix.bytesperline = min;
	min = format.fmt.pix.bytesperline * format.fmt.pix.height;
	if(format.fmt.pix.sizeimage < min)
		format.fmt.pix.sizeimage = min;
	
/*  init mmap begin */	
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_MMAP;
	reqbuf.count = 4;
	
	if(myioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1)
	{
		if(errno == EINVAL)
			fprintf(stderr, "mmap-streaming is not support\n");
		else
			err("VIDIOC_REQBUFS");
	}
	
	if(reqbuf.count < 2 )
	{
		fprintf(stderr, "Not enough buffer memory\n");
		exit(EXIT_FAILURE);
	}
	
	buffers = calloc(reqbuf.count, sizeof(*buffers));
	//assert(buffers != NULL );
	
	for(n_buffers = 0; n_buffers< reqbuf.count; n_buffers++)
	{
		struct v4l2_buffer buffer;
		
		CLEAN(buffer);
		buffer.type = reqbuf.type;
		buffer.memory = V4L2_MEMORY_MMAP;
		buffer.index = n_buffers;
		
		if(myioctl(fd, VIDIOC_QUERYBUF, &buffer) == -1)
			err("VIDIOC_QUERBUF");
		
		buffers[n_buffers].length = buffer.length;
		
		buffers[n_buffers].start = mmap(NULL, buffer.length, prot, MAP_SHARED, fd, buffer.m.offset);
		
		if(buffers[n_buffers].start == MAP_FAILED)
			err("mmap");
	}
/* mmap end */
}

void start_capture(void)
{
	unsigned int i;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	for(i = 0; i< n_buffers; ++i)
	{
		struct v4l2_buffer buf;
		
		CLEAN(buf);
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		
		if(myioctl(fd, VIDIOC_QBUF, &buf) == -1)
			err("VIDIOC_QBUF");
	}
	
	if(myioctl(fd, VIDIOC_STREAMON, &type) == -1)
		err("Streaming on");
}

void stop_capture(void)
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	if(myioctl(fd, VIDIOC_STREAMOFF, &type) == -1)
		err("Streaming off");
}

static int frame_read(FILE *fp)
{
	struct v4l2_buffer buf;
	
	CLEAN(buf);
	
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	
	if(myioctl(fd, VIDIOC_DQBUF, &buf) == -1)				/* DQBUF */
	{
		if(errno == EAGAIN)
			return 0;
		else if(errno == EIO)
			;
		else
			err("VIDIOC_DQBUF");
	}
	
	assert(buf.index < n_buffers);			/* de-queue failed */
	
	fwrite(buffers[buf.index].start, buf.bytesused, 1, fp);		/* write frame */
	
	if(myioctl(fd, VIDIOC_QBUF, &buf) == -1)		/* QBUF */
		err("VIDIOC_QBUF");
	
	return 0;
}

void capture_time(unsigned count, FILE *fp)
{
	while(count-- > 0)
	{
		for(;;)
		{
			fd_set fds;
			struct timeval tv;
			int res;
			
			FD_ZERO(&fds);
			FD_SET(fd, &fds);
			
			tv.tv_sec = 2;
			tv.tv_usec = 0;
			
			res = select(fd + 1, &fds, NULL, NULL, &tv);
			
			if(res == -1)
			{
				if(errno == EINTR)
					continue;
				else
					err("select");
			}
			if(res == 0)
				err("select");
			else
			{
				if(frame_read(fp) == 0)
					break; 				/* read frame without error */
			}
		}
	}
}

void dev_uninit()
{
	unsigned int i;
	
	for(i = 0; i < n_buffers; ++i)
	{
		if(munmap(buffers[i].start, buffers[i].length) == -1)
			err("munmap");
	}
	
	free(buffers);
}
